<!--
 * @Author: JohnYang
 * @Date: 2020-10-13 21:05:39
 * @LastEditors: JohnYang
 * @LastEditTime: 2020-11-08 15:46:27
-->
<script>
  import _ from 'lodash'
  import Vue from 'vue'
  import Component from 'vue-class-component'
  import { Prop, Provide, Watch } from 'vue-property-decorator'
  import FormControl from './form-control'
  import { default as ElementJsonFormItem } from './form-item.vue'
  import { Row, Col } from './config'

  @Component({
    name: 'FormGenerator',
    components: {
      ElementJsonFormItem,
      FormControl,
    },
  })
  export default class FormGenerator extends Vue {
    @Prop({
      type: Object,
      required: true,
    })
    props

    @Prop({
      required: false,
      default: () => {
        return {}
      },
    })
    value

    @Prop({
      type: Object,
      required: false,
      default: () => ({}),
    })
    config

    instances = {}

    rules = {}

    model = {}

    selfProps = {}

    get ins() {
      return this.$refs.form
    }

    created() {
      this.$on('set-value', ({ prop, value }) => {
        this.model[prop] = value
      })
      this.$on('set-attrs', ({ prop, attrs }) => {
        var preAttrs = this.selfProps[prop]
        if (preAttrs) {
          Object.assign(preAttrs, attrs)
        }
      })
      this.$on('ready', (prop, instance) => {
        this.instances[prop] = instance
      })
      this.buildFormItem()
    }

    @Provide('form')
    get form() {
      return this
    }

    @Watch('props')
    onPropsChange() {
      this.transformProps()
      this.mergeValue()
      this.mergeRules()
      this.buildFormItem()
    }

    @Watch('model', { deep: true })
    onModelChange() {
      this.$emit('input', this.model)
    }

    // 由于之前的考虑不合理，base-form撤销内部清理验证状态的问题
    // @Watch('value', { deep: true })
    // onValueChange(val) {
    //   console.log(val)
    //   //完全信任新在值
    //   this.model = val
    //   this.$nextTick(() => {
    //     if (this.ins) {
    //       this.ins.clearValidate()
    //     }
    //   })
    // }

    //在重设字段之后 清理验证状态
    resetFields(model, clearable = true) {
      if (Object.prototype.toString.call(model) === '[object Object]') {
        this.model = model
      } else {
        this.initObject(this.model)
      }
      this.$nextTick(() => {
        clearable && this.ins && this.ins.clearValidate()
      })
    }

    /**
     * 将obj清空 清空规则如下
     */
    initObject(obj) {
      Object.entries(obj).forEach(([prop, objValue]) => {
        if (this.selfProps[prop] && this.selfProps[prop].initValue !== void 0) {
          //如果有初始值的话，则取初始值作为reset的结果
          obj[prop] = this.selfProps[prop].initValue
        } else {
          //否则 按照类型reset
          if (objValue && typeof objValue === 'object') {
            this.initObject(objValue)
          } else if (['number', 'bigint'].includes(typeof objValue)) {
            obj[prop] = 0
          } else if (typeof objValue === 'boolean') {
            obj[prop] = false
          } else if (Array.isArray(objValue)) {
            obj[prop] = []
          } else if (typeof objValue == 'string') {
            obj[prop] = ''
          } else {
            obj[prop] = null
          }
        }
      })
    }

    clearForm() {
      this.initObject(this.model)
      this.ins &&
        this.$nextTick(() => {
          this.ins.clearValidate()
        })
      //将数据同步至外界
      this.$emit('input', this.model)
    }

    validForm() {
      var validate = false
      if (!this.ins) {
        return validate
      }
      var d = this.ins.validate((valid) => {
        validate = valid
      })
      return validate
    }

    /**
     * 递归的转化props
     */
    mergeProps(propValues) {
      propValues.forEach((item) => {
        if (Array.isArray(item.children)) {
          this.mergeProps(item.children)
        } else if (item.children && typeof item.children === 'object') {
          this.mergeProps(
            Object.entries(item.children).map(([prop, value]) => {
              return { ...value, prop }
            })
          )
        } else {
          this.$set(
            this.selfProps,
            item.prop,
            Object.assign(
              {},
              item.layout !== Row ? this.getDefaultProp() : {},
              item
            )
          )
        }
      })
    }

    /**
     * 由于配置的JSON太复杂,因此需要
     */
    getDefaultProp() {
      return {
        labelWidth: this.config.labelWidth,
        span: 24,
        layout: Col,
        showLabel: true,
        visible: true,
        readonly: false,
        disabled: false,
        transformRule: null,
        className: [],
        tag: 'el-input',
        rules: [],
        events: {},
        defaultValue: null,
      }
    }

    /**
     * 生成Form的默认配置
     */
    getDefaultFormConfig() {
      return {
        labelSuffix: '：',
        size: 'medium',
        labelPosition: 'right',
        labelWidth: 100,
        gutter: 15,
        disabled: false,
        span: 24,
        created: '',
        mounted: '',
        beforeDestroy: '',
      }
    }

    /**
     * 将props转化成内部的props并且维护起来
     */
    transformProps() {
      var defaultProp = this.getDefaultProp()
      this.mergeProps(
        Object.entries(this.props).map(([prop, value]) => {
          return {
            ...value,
            prop,
          }
        })
      )
    }

    getInitValue(prop, item) {
      //以value为准
      if (typeof this.model[prop] !== 'undefined') {
        return this.model[prop]
      } else if (
        this.selfProps[prop] &&
        typeof this.selfProps[prop].defaultValue !== 'undefined'
      ) {
        return this.selfProps[prop].defaultValue
      } else if (item && typeof item.defaultValue !== 'undefined') {
        return item.defaultValue
      }
      return this.selfProps[prop] && this.selfProps[prop].multiple ? [] : ''
    }

    _setModel(modelsValue) {
      modelsValue.forEach((item) => {
        if (Array.isArray(item.children)) {
          this._setModel(item.children)
        } else if (item.children && typeof item.children === 'object') {
          this._setModel(
            Object.entries(item.children).map(([prop, value]) => {
              return { ...value, prop }
            })
          )
        } else {
          this.$set(this.model, item.prop, this.getInitValue(item.prop, item))
        }
      })
    }

    setModel() {
      this._setModel(
        Object.entries(this.selfProps).map(([prop, item]) => {
          return {
            ...item,
            prop,
          }
        })
      )
    }

    _setRules(propsValue) {
      propsValue.forEach((value) => {
        if (Array.isArray(value.children)) {
          this._setRules(value.children)
        } else if (typeof value.children === 'object' && value.children) {
          this._setRules(
            Object.entries(item.children).map(([prop, value]) => {
              return { ...value, prop }
            })
          )
        } else {
          this.$set(
            this.rules,
            value.prop,
            this.selfProps[value.prop].rules || []
          )
        }
      })
    }

    setRules() {
      this._setRules(
        Object.entries(this.selfProps).map(([prop, value]) => {
          return {
            prop,
            ...value,
          }
        })
      )
    }

    /**
     * 合并value，以value的初始值为准
     */
    mergeValue() {
      Object.entries(this.value).forEach(([prop, value]) => {
        this.$set(this.model, prop, value)
      })
    }

    mergeRules() {
      var rules = this.$attrs.rules
      if (!rules) {
        return
      }
      Object.entries(rules).forEach(([prop, rules]) => {
        var propRule = this.selfProps[prop].rules
        // 把attrs上的rule覆盖给item上的rule
        if (Array.isArray(rules)) {
          propRule = rules
        }
        // 如果两者都是对象的话
        else if (typeof rules == 'object' && !Array.isArray(propRule)) {
          Object.assign(propRule, rules)
        }
      })
    }

    removeComment(str) {
      return str
        .replace(/\/\/\[\w\W]*(?<=\n)/g, '')
        .replace(/\/\*[\w\W]*\*\//g, '')
        .replace(/^\n/, '')
    }

    parseFunc(str) {
      var func
      try {
        func = new Function('return ' + this.removeComment(str))()
      } catch (exp) {
        console.error(exp)
        func = function () {
          console.warn(str)
        }
      }
      return func
    }

    buildFormItem() {
      this.transformProps()
      this.mergeValue()
      this.setModel()
      this.setRules()
      this.coverRules()
      this.coverEvents()
    }

    /**
     * 将外界的rule转变为el-form-item 可以识别的rule
     */
    transformRule(rule) {
      if (typeof rule.validator === 'function') {
        var validator = rule.validator

        if (typeof validator === 'string' && validator !== '') {
          validator = this.parseFunc(validator)
        }
        rule.validator = (rule, value, callback) => {
          return validator(rule, value, callback, this.model)
        }
      }
    }

    coverRules() {
      Object.entries(this.rules).forEach(([prop, rule]) => {
        if (Array.isArray(rule)) {
          rule.forEach((item) => {
            this.transformRule(item)
          })
        } else {
          this.transformRule(rule)
        }
      })
    }

    /**
     * 从后台读取的内容 进行反序列化
     */
    coverEvents() {
      var _this = this
      Object.entries(this.selfProps).forEach(([prop, item]) => {
        if (!item.events) {
          return
        }
        var events = {}
        Object.entries(item.events).forEach(([eventName, func]) => {
          if (typeof func === 'string') {
            var newFunc = this.parseFunc(func)
            events[eventName] = function () {
              return newFunc.apply(_this, [
                _this.model[prop],
                _this.model,
                _this.instances,
              ])
            }
          } else {
            events[eventName] = function () {
              return func.apply(_this, [
                _this.model[prop],
                _this.model,
                _this.instances,
              ])
            }
          }
        })
        this.selfProps[prop].events = events
      })
    }

    customRender(args) {
      var slot = this.$scopedSlots[args.prop]
      let labelWidth = args.labelWidth ? `${args.labelWidth}px` : null
      return (
        <el-col span={args.span || 24}>
          <el-form-item
            prop={args.prop}
            label-width={labelWidth}
            rules={args.rules}
            label={args.showLabel ? args.label : ''}
          >
            {slot({ model: this.model, ...args })}
          </el-form-item>
        </el-col>
      )
    }

    render(h) {
      var { labelWidth } = Object.assign(
        {},
        this.config,
        this.getDefaultFormConfig()
      )
      return (
        <el-form
          ref="form"
          class="el-form-generator"
          {...{
            attrs: this.$attrs,
            props: {
              ...this.$attrs,
              ...this.config,
              labelWidth: labelWidth ? `${labelWidth}px` : null,
              model: this.model,
              rules: this.rules,
            },
            on: {
              ...this.$listeners,
            },
          }}
        >
          {Object.entries(this.selfProps)
            .filter(([prop, item]) => {
              return item.visible
            })
            .map(([prop, item]) => {
              var props = {}
              if (typeof this.model[prop] !== 'undefined') {
                props.value = this.model[prop]
              }
              var slot = this.$scopedSlots[prop]
              return (
                <element-json-form-item
                  {...{
                    props: {
                      ...props,
                      sloter: typeof slot == 'function' && this.customRender,
                    },
                    on: {
                      input: (val) => {
                        if (item.layout !== Row) {
                          this.model[prop] = val
                          this.$emit('input', this.model)
                        }
                      },
                    },
                  }}
                  key={prop}
                  item={{ ...item, prop }}
                />
              )
            })}
        </el-form>
      )
    }
  }
</script>

<style lang="scss" scoped>
  .el-form-generator {
    display: inline-block;
    width: 100%;
  }
</style>
